package cate.game.role.bag.hero;


import cate.common.table.d.GDAttr;
import cate.common.table.d.GDHero;
import cate.common.table.d.GDObj;
import cate.common.table.item.hero.HeroExcel;
import cate.common.table.item.hero.row.HeroBaseRow;
import cate.common.table.item.hero.row.HeroGradeRow;
import cate.common.table.item.rune.row.RunePosRow;
import cate.common.table.item.staruptask.HeroStarTaskRow;
import cate.common.util.GameResult;
import cate.game.GameBody;
import cate.game.attr.FightAttr;
import cate.game.attr.HeroExAttrPart;
import cate.game.attr.PowerAttrPart;
import cate.game.client.msg.MergeMsgSender;
import cate.game.play.part.BattleHero;
import cate.game.play.support.PlayBuildContext;
import cate.game.res.MixRes;
import cate.game.res.RewardShow;
import cate.game.role.Role;
import cate.game.role.bag.awakenfairy.AwakenFairy;
import cate.game.role.bag.awakenfairy.AwakenFairiesOn;
import cate.game.role.bag.equip.Equip;
import cate.game.role.bag.equip.EquipsOn;
import cate.game.role.bag.exp.ExpsOn;
import cate.game.role.bag.fairy.FairiesOn;
import cate.game.role.bag.fairy.Fairy;
import cate.game.role.bag.favorability.FavorabilityOn;
import cate.game.role.bag.furniture.Furniture;
import cate.game.role.bag.furniture.FurnituresOn;
import cate.game.role.bag.inscription.InscriptionPage;
import cate.game.role.bag.inscription.InscriptionPageOn;
import cate.game.role.bag.item.BaseBagItem;
import cate.game.role.bag.kitchenware.Kitchenware;
import cate.game.role.bag.kitchenware.KitchenwaresOn;
import cate.game.role.bag.newtalent.NewTalentOn;
import cate.game.role.bag.rune.Rune;
import cate.game.role.bag.rune.RunesOn;
import cate.game.role.bag.spice.Spice;
import cate.game.role.bag.spice.SpicesOn;
import cate.game.train.equip.msg.HeroEquipOffResp;
import cate.game.train.furniture.msg.FurnitureOffResp;
import cate.game.train.rune.msg.RuneOffResp;
import cate.game.train.yoke.msg.YokeOffAllResp;
import cate.game.util.GameKit;
import com.fasterxml.jackson.annotation.JsonIgnore;
import cp.solution.pattern.ICopiable;
import cp.solution.util.collection.ListKit;
import easy.java.dev.note.NoteField;
import easy.java.practice.errorcode.EcResult;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

/**
 * 英雄背包里的英雄项
 */
@Slf4j
public class Hero extends BaseBagItem implements ICopiable<Hero> {
  @NoteField("等级")
  public int level;

  @NoteField("共享等级")
  public int shareLevel;

  @NoteField("英雄阶数")
  public int grade;

  @NoteField("共享阶数")
  public int shareGrade;

  @NoteField(value = "该英雄身上的装备")
  public EquipsOn equipsOn;

  @NoteField(value = "英雄身上的灵器")
  public FairiesOn fairiesOn;

  @NoteField(value = "英雄身上的徽章")
  public RunesOn runesOn;

  @NoteField(value = "英雄的手办")
  public FurnituresOn furnituresOn;

  @NoteField(value = "英雄的星格")
  public SpicesOn spicesOn;

  @NoteField(value = "英雄的天赋")
  public NewTalentOn newTalentOn;

  @NoteField(value = "英雄的好感度")
  public FavorabilityOn favorabilityOn;

  @NoteField(value = "铭文页")
  public InscriptionPageOn inscriptionPageOn;

  @NoteField(value = "羁绊英雄")
  public YokeOn yokeOn;

  @NoteField("英雄的心得")
  public ExpsOn expsOn;

  @NoteField("觉醒灵器")
  public AwakenFairiesOn awakenFairiesOn;

  @NoteField("厨具")
  public KitchenwaresOn kitchenwaresOn;

  @NoteField("锁")
  public HeroLock lock;

  @NoteField(value = "英雄的升星任务完成情况")
  public List<Integer> starTaskList;

  @NoteField(value = "英雄在背包里的战力", detail = "不包括上阵后的加成")
  @Setter
  @Getter
  private long power;

  @NoteField(value = "英雄在背包里的属性", detail = "不包括上阵后的加成")
  @JsonIgnore
  @Setter
  public FightAttr attr;

  @NoteField("当前战力准确性是否失效")
  private boolean isPowerExpire = true;

  @NoteField(value = "附加属性模块", detail = "记录没有role时无法计算的属性 如羁绊、星格 适用于机器人和援助英雄")
  public HeroExAttrPart exPart;

  public Hero() {

  }

  public Hero(Role role, int tid) {
    super(role, GDObj.Type.HERO, tid, 1);
    initialize(role);
  }

  @Override
  public void initialize(Role role) {
    super.initialize(role);
    if (level < GDHero.LEVEL_MIN) {
      level = GDHero.LEVEL_MIN;
    }
    if (level > GDHero.LEVEL_MAX) {
      level = GDHero.LEVEL_MAX;
    }
    if (grade < GDHero.GRADE_MIN) {
      grade = GDHero.GRADE_MIN;
    }
    if (grade > GDHero.GRADE_MAX) {
      grade = GDHero.GRADE_MAX;
    }
    if (lock == null) {
      lock = new HeroLock();
    }
    lock.initialize(this.uid, GDObj.Type.HERO);
    if (equipsOn == null) {
      equipsOn = new EquipsOn();
    }
    equipsOn.initialize(this, role);
    if (fairiesOn == null) {
      fairiesOn = new FairiesOn();
    }
    fairiesOn.initialize(this, role);
    if (runesOn == null) {
      runesOn = new RunesOn();
    }
    runesOn.initialize(this, role);
    if (spicesOn == null) {
      spicesOn = new SpicesOn();
    }
    spicesOn.initialize(this, role);
    if (furnituresOn == null) {
      furnituresOn = new FurnituresOn();
    }
    furnituresOn.initialize(this, role);
    if (newTalentOn == null) {
      newTalentOn = new NewTalentOn();
    }
    newTalentOn.initialize(this, role);
    if (starTaskList == null) {
      starTaskList = new ArrayList<>();
    }
    if (favorabilityOn == null) {
      favorabilityOn = new FavorabilityOn();
    }
    favorabilityOn.initialize(this, role);
    if (inscriptionPageOn == null) {
      inscriptionPageOn = new InscriptionPageOn();
    }
    inscriptionPageOn.initialize(this, role);
    if (yokeOn == null) {
      yokeOn = new YokeOn();
    }
    yokeOn.initialize(this, role);
    if (exPart == null) {
      exPart = new HeroExAttrPart();
    }
    if (expsOn == null) {
      expsOn = new ExpsOn();
    }
    expsOn.initialize(this, role);
    if (awakenFairiesOn == null) {
      awakenFairiesOn = new AwakenFairiesOn();
    }
    awakenFairiesOn.initialize(this, role);
    if (kitchenwaresOn == null)  {
      kitchenwaresOn = new KitchenwaresOn();
    }
    kitchenwaresOn.initialize(this, role);
  }

  @JsonIgnore
  public HeroBaseRow getTpl(HeroExcel heroExcel) {
    HeroBaseRow tpl = heroExcel.base.get(this.tid);
    if (tpl == null) {
      log.error("策划配置错误，缺少id={}的英雄基础", tid);
    }
    return tpl;
  }

  @JsonIgnore
  @Override
  public HeroBaseRow getTpl(PlayBuildContext ctx) {
    return getTpl(ctx.heroExcel());
  }

  @Override
  public HeroBaseRow getTpl(GameBody game) {
    return getTpl(game.fight.getPlayBuildContext());
  }

  @JsonIgnore
  public int getEffectiveLevel() {
    return Math.max(this.level, this.shareLevel);
  }

  @JsonIgnore
  public int getEffectiveGrade() {
    return Math.max(this.grade, this.shareGrade);
  }

  public Hero inherit(int tid) {
    Hero hero = copy();
    hero.uid = GameKit.uid();
    hero.tid = tid;
    hero.shareGrade = 0;
    hero.shareLevel = 0;
    hero.initialize(role);
    hero.yokeOn.recheck();
    hero.expsOn.recheck();
    hero.spicesOn.recheck();
    hero.starTaskList.clear();
    return hero;
  }

  @Override
  public Hero copy() {
    Hero cp = new Hero();
    super.copy(cp);
    cp.level = this.level;
    cp.shareLevel = this.shareLevel;
    cp.grade = this.grade;
    cp.shareGrade = this.shareGrade;
    cp.furnituresOn = this.furnituresOn.copy();
    cp.spicesOn = this.spicesOn.copy();
    cp.equipsOn = this.equipsOn.copy();
    cp.runesOn = this.runesOn.copy();
    cp.fairiesOn = this.fairiesOn.copy();
    cp.yokeOn = this.yokeOn.copy();
    if (this.newTalentOn != null) {
      cp.newTalentOn = this.newTalentOn.copy();
    }
    if (this.favorabilityOn != null) {
      cp.favorabilityOn = this.favorabilityOn.copy();
    }
    cp.power = this.power;
    cp.attr = this.attr == null ? null : this.attr.copy();
    cp.starTaskList = new ArrayList<>(this.starTaskList);
    if (this.inscriptionPageOn != null) {
      cp.inscriptionPageOn = this.inscriptionPageOn.copy();
    }
    cp.exPart = this.exPart == null ? null : this.exPart.copy();
    if (this.expsOn != null) {
      cp.expsOn = this.expsOn.copy();
    }
    if (this.awakenFairiesOn != null) {
      cp.awakenFairiesOn = this.awakenFairiesOn.copy();
    }
    if (this.kitchenwaresOn != null) {
      cp.kitchenwaresOn = this.kitchenwaresOn.copy();
    }
    cp.gainExtraData();
    return cp;
  }

  /**
   * 获取英雄身上各个穿戴的模块
   *
   * @return its
   */
  @JsonIgnore
  private OnHeroItem[] getHeroItems() {
    return new OnHeroItem[]{equipsOn, fairiesOn, runesOn, furnituresOn, awakenFairiesOn, kitchenwaresOn};
  }

  /**
   * 获取英雄自身基础的技能
   *
   * @return sks
   */
  @JsonIgnore
  public List<Integer> getSelfSkills(PlayBuildContext pbc) {
    HeroBaseRow baseRow = getTpl(pbc.heroExcel());
    if (baseRow == null) {
      return new ArrayList<>();
    }
    int gradeEnableNum = 1;
    HeroGradeRow gradeTpl = pbc.heroExcel().grade.find(baseRow.protoId, this.getEffectiveGrade());
    if (gradeTpl != null) {
      gradeEnableNum = gradeTpl.skillEnableNum;
    }
    List<Integer> skillList = new ArrayList<>(baseRow.skillList);
    int skillEnableNum = Math.min(skillList.size(), gradeEnableNum);
    skillList = ListKit.trimMaxKeepHead(skillList, skillEnableNum);

    //觉醒技能
    skillList.addAll(baseRow.taskSkillList);
    for (Integer taskId : this.starTaskList) {
      HeroStarTaskRow taskRow = pbc.starUpTaskExcel().task.get(taskId);
      if (taskRow != null) {
        skillList.addAll(taskRow.skillList);
      }
    }
    return skillList;
  }

  /**
   * 获取英雄自身基础的属性
   *
   * @return attr
   */
  @JsonIgnore
  public FightAttr getSelfAttr(PlayBuildContext ctx) {
    HeroBaseRow row = this.getTpl(ctx);
    if (row == null) {
      return new FightAttr();
    }

    //英雄属性=初始属性
    // + 英雄等级 * 成长属性 * （进阶系数^英雄阶数) * （星级系数 + 升星任务系数)
    // + 进阶固定属性 * 进阶次数
    // + 升星固定属性 * 升星次数
    // + 任务固定属性

    FightAttr total = new FightAttr();
    //初始属性
    total.add(new FightAttr(row.attrInitStr));

    //成长属性
    FightAttr growAttr = new FightAttr();
    //星级系数
    Map<Integer,Double> growScale = new HashMap<>();

    // 等级成长属性
    if (StringUtils.isNotBlank(row.growStr)) {
      growAttr.add(new FightAttr(row.growStr).scale(this.getEffectiveLevel()));
      for (Map.Entry<Integer, Double> scaleEntry : row.gradeScaleMap.entrySet()) {
        growAttr.scale(scaleEntry.getKey(), Math.pow(scaleEntry.getValue(), getEffectiveGrade()));
      }
      mergeMap(growScale,row.starScaleMap);
    }

    //进阶属性
    if (getEffectiveGrade() > 0 && StringUtils.isNotBlank(row.gradeAttrStr)) {
      total.add(new FightAttr(row.gradeAttrStr).scale(getEffectiveGrade()));
    }
    //升星属性
    if (StringUtils.isNotBlank(row.intrinsicStr)) {
      total.add(new FightAttr(row.intrinsicStr));
    }

    if (CollectionUtils.isNotEmpty(this.starTaskList)) {
      for (int taskId : this.starTaskList) {
        HeroStarTaskRow taskRow = ctx.starUpTaskExcel().task.get(taskId);
        if (taskRow == null) {
          continue;
        }
        if (StringUtils.isNotBlank(taskRow.stableStr)) {
          total.add(new FightAttr(taskRow.stableStr));
        }
        mergeMap(growScale,taskRow.growMap);
      }
    }
    //移除灵能属性
    for (int attr : GDAttr.PSY_ATTR) {
      if(!growScale.containsKey(attr)){
        growAttr.remove(attr);
      }
    }
    //放大成长属性
    for (Map.Entry<Integer, Double> starAttr : growScale.entrySet()) {
      growAttr.scale(starAttr.getKey(), starAttr.getValue());
    }
    total.add(growAttr);
    return total;
  }

  private void mergeMap(Map<Integer, Double> oldMap, Map<Integer, Double> newMap) {
    for (Map.Entry<Integer, Double> entry : newMap.entrySet()) {
      oldMap.put(entry.getKey(), oldMap.getOrDefault(entry.getKey(), 0d) + entry.getValue());
    }
  }

  /**
   * 获取英雄附加属性技能
   *
   * @param ctx
   * @return
   */
  @JsonIgnore
  public PowerAttrPart getPowerAttrPart(PlayBuildContext ctx) {
    PowerAttrPart allPart = new PowerAttrPart();
    forHeroItems(a -> allPart.merge(a.getPowerAttrPart(ctx)));
    allPart.merge(newTalentOn.getPowerAttrPart(ctx));
    if (favorabilityOn != null) {
      allPart.merge(favorabilityOn.getPowerAttrPart(ctx));
    }
    if (getRole() != null) {
      allPart.merge(buildAttrPartWithRole(ctx));
    }
    if (exPart != null) {
      allPart.merge(exPart);
    }
    //新加模块需要非空判断 否则老数据会报错`
    return allPart;
  }

  //需要依赖role计算战力的模块
  private PowerAttrPart buildAttrPartWithRole(PlayBuildContext ctx) {
    PowerAttrPart part = new PowerAttrPart();
    if (spicesOn != null) {
      part.merge(spicesOn.getPowerAttrPart(ctx));
    }
    if (inscriptionPageOn != null) {
      part.merge(inscriptionPageOn.getPowerAttrPart(ctx));
    }
    if (yokeOn != null) {
      part.merge(yokeOn.getPowerAttrPart(ctx));
    }
    if (expsOn != null) {
      part.merge(expsOn.getPowerAttrPart(ctx));
    }
    return part;
  }

  //记录依赖role计算战力的模块
  public void recordAttrPartWithRole(PlayBuildContext ctx) {
    if (exPart == null) {
      exPart = new HeroExAttrPart();
    }
    exPart.readFrom(buildAttrPartWithRole(ctx));
  }


  /**
   * 对英雄身上装备系统做遍历
   */
  public void forHeroItems(Consumer<OnHeroItem> action) {
    for (OnHeroItem heroItem : this.getHeroItems()) {
      if (heroItem != null) {
        action.accept(heroItem);
      }
    }
  }

  public GameResult<Byte> onEquip(PlayBuildContext ctx, Equip e) {
    return equipsOn.on(ctx, this, e.getTpl(ctx).pos, e);
  }

  public GameResult<Byte> offEquip(PlayBuildContext ctx, byte pos) {
    return equipsOn.off(ctx, this, pos, false);
  }

  public GameResult<Byte> onFairy(PlayBuildContext ctx, Fairy e, byte pos) {
    return fairiesOn.on(ctx, this, pos, e);
  }

  public GameResult<Byte> offFairy(PlayBuildContext ctx, byte pos) {
    return fairiesOn.off(ctx, this, pos, true);
  }

  public GameResult<Byte> onRune(PlayBuildContext ctx, Rune e, byte pos) {
    return runesOn.on(ctx, this, pos, e);
  }

  public GameResult<Byte> offRune(PlayBuildContext ctx, byte pos) {
    return runesOn.off(ctx, this, pos, true);
  }

  public GameResult<Byte> onFurniture(PlayBuildContext ctx, Furniture e, byte pos, MergeMsgSender sender) {
    return furnituresOn.on(ctx, this, pos, e, sender);
  }

  public GameResult<Byte> offFurniture(PlayBuildContext ctx, byte pos, MergeMsgSender sender) {
    return furnituresOn.off(ctx, this, pos, true, sender);
  }

  public GameResult<Byte> onYoke(PlayBuildContext ctx, byte pos, Hero slave) {
    return yokeOn.on(ctx, pos, slave);
  }

  public GameResult<Byte> offYoke(byte pos) {
    return yokeOn.off(pos);
  }

  public GameResult<Byte> onExp(PlayBuildContext ctx, int tid, byte pos) {
    return expsOn.on(ctx, pos, tid);
  }

  public GameResult<Byte> offExp(PlayBuildContext ctx, byte pos) {
    return expsOn.off(ctx, pos);
  }

  public GameResult<Byte> offAllYoke() {
    return yokeOn.offAll();
  }

  public GameResult<Byte> onAwakenFairy(PlayBuildContext ctx, AwakenFairy e, byte pos) {
    return awakenFairiesOn.on(ctx, this, pos, e);
  }

  public GameResult<Byte> offAwakenFairy(PlayBuildContext ctx, byte pos) {
    return awakenFairiesOn.off(ctx, this, pos, true);
  }

  public GameResult<Byte> onKitchenware(PlayBuildContext ctx, Kitchenware e, byte pos) {
    return kitchenwaresOn.on(ctx, this, pos, e);
  }

  public GameResult<Byte> offKitchenware(PlayBuildContext ctx, byte pos) {
    return kitchenwaresOn.off(ctx, this, pos, true);
  }

  /**
   * 根据等级下装备
   *
   * @param gameBody gb
   * @return rs
   */
  public RewardShow offSthByLevel(GameBody gameBody) {
    RewardShow show = new RewardShow();
    //徽章
    if (!runesOn.getPosItems().isEmpty()) {
      Map<Byte, Rune> offMap = new HashMap<>();
      for (byte pos : runesOn.getPosItems().keySet()) {
        RunePosRow posRow = gameBody.table.rune.pos.getRowByPos(pos);
        if (posRow == null || getEffectiveLevel() < posRow.level) {
          offMap.put(pos, runesOn.getPosItem(pos));
        }
      }
      offMap.forEach((pos, e) -> {
        if (runesOn.off(role, this, pos, false).ok()) {
          show.addBagItem(role.getGame(), e);
        }
      });
    }
    return show;
  }

  public RewardShow offAll(Role role, MergeMsgSender sender) {
    RewardShow rewardShow = new RewardShow();
    rewardShow.merge(equipsOn.offAll(role, this, sender, false));
    rewardShow.merge(runesOn.offAll(role, this, sender, false));
    rewardShow.merge(fairiesOn.offAll(role, this, sender, false));
    rewardShow.merge(spicesOn.offAll(role, this, sender, false));
    rewardShow.merge(furnituresOn.offAll(role, this, sender, false));
    if (favorabilityOn != null) {
      rewardShow.merge(resetFavorability(sender, false));
    }
    if (expsOn != null) {
      expsOn.offAll(role, this, sender, false);
    }
    if (awakenFairiesOn != null) {
      rewardShow.merge(awakenFairiesOn.offAll(role, this, sender, false));
    }
    if (kitchenwaresOn != null) {
      rewardShow.merge(kitchenwaresOn.offAll(role, this, sender, false));
    }
    if (yokeOn != null) {
      yokeOn.offAll();
      role.sendNow(new YokeOffAllResp(uid));
    }
    return rewardShow;
  }

  public RewardShow offAllEquip(Role role) {
    List<Byte> posList = new ArrayList<>(equipsOn.equipMap.keySet());
    if (posList.isEmpty()) {
      return new RewardShow();
    }
    RewardShow rewardShow = equipsOn.offAll(role, this, false);
    role.sendNow(new HeroEquipOffResp(uid, posList));
    return rewardShow;
  }

  public RewardShow offAllRune(Role role) {
    List<Byte> posList = new ArrayList<>(runesOn.runeMap.keySet());
    if (posList.isEmpty()) {
      return new RewardShow();
    }
    RewardShow rewardShow = runesOn.offAll(role, this, false);
    posList.forEach(e -> role.sendNow(new RuneOffResp(uid, e)));
    return rewardShow;
  }

  /**
   * 这个方法满了会发邮件
   *
   * @param role role
   * @param pos  pos
   * @return e
   */
  public GameResult<Byte> offSpice(Role role, byte pos) {
    return spicesOn.off(role, this, pos, true);
  }

  public GameResult<Byte> offSpice(Role role, String spiceUid) {
    return spicesOn.off(role, this, spiceUid, true);
  }

  public GameResult<Byte> onSpice(PlayBuildContext ctx, Spice e, byte pos) {
    return spicesOn.on(ctx, this, pos, e);
  }

  public GameResult<Hero> onInscriptionPage(InscriptionPage page) {
    return inscriptionPageOn.on(page, this, true);
  }

  public GameResult<Hero> offInscriptionPage() {
    return inscriptionPageOn.off(this, true);
  }

  public EcResult<Void> improveFavorability(MixRes favorabilityItems) {
    return favorabilityOn.improve(favorabilityItems);
  }

  public RewardShow resetFavorability(MergeMsgSender sender, boolean updatePower) {
    return favorabilityOn.resetFavorability(sender, updatePower);
  }

  /**
   * 标记战力失效
   */
  public void markPowerExpire() {
    isPowerExpire = true;
  }

  public long getPower(PlayBuildContext pbc) {
    if (isPowerExpire || attr == null) {
      calPowerAndAttr(pbc);
    }
    return power;
  }


  private void calPowerAndAttr(PlayBuildContext pbc) {
    BattleHero bh = pbc.heroBuilder().buildInBag(role, this);
    this.power = bh.power;
    this.attr = bh.getAttr();
    isPowerExpire = false;
  }

  /**
   * 设置共享等级
   *
   * @param shareLevel sl
   */
  public void setShareLevel(Role role, int shareLevel, int shareGrade) {
    HeroBaseRow tpl = getTpl(role.getGame().fight.getPlayBuildContext());
    int heroProtoMaxLvl = role.getGame().table.hero.base.checkMaxStarLvlLimite(tpl.protoId);
    // 是否可以冲破等级上限
    this.shareLevel = tpl.isBreakLimit() ? shareLevel : Math.min(heroProtoMaxLvl, shareLevel);
    lock.heroShare = shareLevel != 0;
    this.shareGrade = Math.min(shareGrade, tpl.gradeMax);
    this.markPowerExpire();
  }

  @Override
  public String toDetail() {
    return String.format("%d|%d|%d", getEffectiveLevel(), getEffectiveGrade(), power);
  }

  public void gainExtraData() {
    if (spicesOn != null) {
      spicesOn.gainSpiceData();
    }
  }

  public void clearExtraData(){
    spicesOn.clearSpiceData();
  }
}
